import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  Input,
  OnChanges,
  OnInit,
  QueryList,
  Renderer2,
  SimpleChanges,
  TemplateRef,
  ViewChildren,
  ViewEncapsulation,
} from '@angular/core';
import {
  animate,
  AnimationBuilder,
  AnimationFactory,
  AnimationPlayer,
  style,
  transition,
  trigger,
} from '@angular/animations';
import { InputBoolean, InputNumber, MotionDuration, MotionFunc, noAnimationToVoid, TypeUtils } from '../core/api';

@Component({
  selector: 'lv-badge',
  exportAs: 'lvBadge',
  templateUrl: './badge.component.html',
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    '[class.lv-badge]': 'true',
    '[class.lv-badge-round]': 'lvDot',
  },
  animations: [
    trigger('showBadge', [
      noAnimationToVoid,
      transition(':enter', [
        style({ transform: 'scale(0)' }),
        animate(`${MotionDuration.SM} ${MotionFunc.EASE_OUT}`, style({ transform: 'scale(1)' })),
      ]),
      transition(':leave', [
        style({ transform: 'scale(1)' }),
        animate(`${MotionDuration.XS} ${MotionFunc.EASE_OUT}`, style({ transform: 'scale(0)' })),
      ]),
    ]),
    trigger('showPlus', [
      noAnimationToVoid,
      transition(':enter', [
        style({ transform: 'translateY(100%)' }),
        animate(`${MotionDuration.SM} ${MotionFunc.EASE}`, style({ transform: 'translateY(0)' })),
      ]),
      transition(':leave', [
        style({ transform: 'translateY(0)' }),
        animate(`${MotionDuration.SM} ${MotionFunc.EASE}`, style({ transform: 'translateY(100%)' })),
      ]),
    ]),
  ],
})
export class BadgeComponent implements OnInit, OnChanges {
  @Input() lvTextColor: string = '';

  @Input() @InputNumber() lvValue = 0;

  @Input() @InputBoolean() lvDot = false;

  @Input() lvBackgroundColor: string = '';

  @Input() @InputNumber() lvMaxValue = 999;

  @Input() @InputBoolean() lvVisible = true;

  @Input() @InputBoolean() lvShowZero = false;

  @Input() lvLabel: string | TemplateRef<void> = null;

  @Input() lvAsStatus: 'success' | 'processing' | 'default' | 'error' | 'warning' = null;

  @ViewChildren('lvBadgeValue', { read: ElementRef }) lvBadgeValue: QueryList<ElementRef>;

  // 用于处理动效方向
  public lastValue = 0;
  public valueArray = [];
  public maxValueArray = [];
  public typeUtils = TypeUtils;

  countArray = [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0];

  constructor(
    public cdr: ChangeDetectorRef,
    public el: ElementRef,
    private renderer: Renderer2,
    private animationBuilder: AnimationBuilder,
  ) {}

  ngOnInit(): void {
    this.getMaxValueArray();
  }

  getMaxValueArray(): void {
    this.maxValueArray = this.lvMaxValue.toString().split('');
  }

  showBadge(): boolean {
    return this.lvValue > 0 || (this.lvValue === 0 && this.lvShowZero);
  }

  getValueArray(): void {
    this.valueArray = this.lvValue.toString().split('');
    setTimeout((): void => {
      if (!this.lvBadgeValue) {
        return;
      }
      this.setScrollAnimation();
    });
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.lvValue) {
      this.lvValue = Math.max(0, changes.lvValue.currentValue);
      this.getValueArray();
    }
    if (changes.lvMaxValue) {
      this.lvMaxValue = Math.max(0, changes.lvMaxValue.currentValue);
      this.getMaxValueArray();
    }
  }

  private getYPosition(index: number): string {
    return `translateY(${-index * 10}%)`;
  }

  private numberScrollDone(element: HTMLElement, i: number): void {
    this.renderer.setStyle(element, 'transform', `translateY(${-(Number(this.valueArray[i]) + 1) * 10}%)`);
  }

  private getScrollAnimation({ last, current, up }: { last: number; current: number; up: boolean }): AnimationFactory {
    const max = this.countArray[0];
    const maxIndex = this.countArray.length - 1;
    let end = current + 1;
    if (!current && last && up) {
      // current 为 0
      end = maxIndex;
    } else if (current === max && last !== current && !up) {
      // current 为 9
      end = 0;
    }
    return this.animationBuilder.build([
      style({ transform: this.getYPosition(last + 1) }),
      animate(`${MotionDuration.SM} ${MotionFunc.EASE}`, style({ transform: this.getYPosition(end) })),
    ]);
  }

  private setScrollAnimation(): void {
    const elements = Array.from(this.lvBadgeValue)
      .map((item): any => item.nativeElement)
      .reverse();
    const len = Math.max(this.lastValue.toString().length, this.lvValue.toString().length);
    const lastValueArray = this.lastValue
      .toString()
      .padStart(len, '0')
      .split('')
      .map((item): number => Number(item));
    const valueArray = this.lvValue
      .toString()
      .padStart(len, '0')
      .split('')
      .map((item): number => Number(item));
    const animations = new Array(len)
      .fill(null)
      .map(
        (_, i): AnimationFactory =>
          this.getScrollAnimation({
            last: lastValueArray[i],
            current: valueArray[i],
            up: this.lvValue > this.lastValue,
          }),
      )
      .reverse();
    const player = elements.map((item, i): AnimationPlayer => animations[i].create(item));
    player.forEach((item, i): void => {
      item.onDone((): void => {
        this.numberScrollDone(elements[i], i);
      });
      item.play();
    });
    this.lastValue = this.lvValue;
  }
}
